﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using System.Collections.ObjectModel;
using System.Xml;
using System.Xml.XPath;


using log4net;
using CommonLib;


namespace RedfinCrawlPro
{

    public class GeoCoordinate
    {
        private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
        public GeoCoordinate() { }
        public GeoCoordinate(double lat, double Long)
        {
            this.Lat = lat;
            this.Long = Long;
        }

        public GeoCoordinate ( GeoCoordinate pt )
        {
            Lat = pt.Lat;
            Long = pt.Long;
        }

        

        public double Lat   { get; set; }
        public double Long { get; set; } 
    }


    public class GeoRect
        {
        public GeoCoordinate northWest { get; set; }
        public GeoCoordinate northEast { get; set; }
        public GeoCoordinate southWest { get; set; }
        public GeoCoordinate southEast { get; set; }

        
        }


    public class RedfinConfig: FetcherConfigBaseRec                                                                                                 
    {
        private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
        private const string nameAttrString = "name";
        private const string typeAttrStr = "type";
        private const string walkScoreAttrString = "walkScore";
        private const string crimeScoreAttrString = "crimeScore";
        private const string zillowQueryAttrString = "zillowQuery";        
        private const string activeAttrString = "active";
        private const string GeoPointString = "./GeoPolygon/geoPoint";
        private const string GeoRadiusString = "./GeoPointAndRadius";
        private const string latString = "lat";
        private const string longString = "long";
        private const string radiusString = "radius";
        private const string cityNameString = "./CityName";

        private const string processorString = "./Processors/processor";

        public bool WalkScore { get; private set; }
        public bool CrimeScore { get; private set; }
        public bool zillowRegionStats { get; private set; }
        public string cityName { get; private set; }

        private   List<GeoCoordinate>        _polygonGeopoints { get;  set; }


        public ReadOnlyCollection<GeoCoordinate> PolygonGeopoints
        {
            get
            {
                ReadOnlyCollection<GeoCoordinate> readOnlyCollection = new ReadOnlyCollection<GeoCoordinate>(_polygonGeopoints);
                return readOnlyCollection;
            }

        }

        public RedfinConfig()
        {
            _polygonGeopoints = new List<GeoCoordinate>();
            WalkScore  = false;
            CrimeScore = false;
            zillowRegionStats = false;
        }

        public bool LoadConfiguration(XmlNode fetcherNode)
        {
            XmlAttribute xmlAttr = null;

            Console.Write("Reading Fetcher Record  ");

            // read fetcher Name Attribute 
            xmlAttr = fetcherNode.Attributes[nameAttrString];
            if (xmlAttr == null)
            {
                log.DebugFormat("AttributeMissing : " + nameAttrString +" :  For fetcher Record ");
                return false;
            }
            _FetcherName = xmlAttr.Value;

            log.DebugFormat("Name: " + FetcherName); 

            //  Walk Score Attribute
            xmlAttr = fetcherNode.Attributes[walkScoreAttrString];
            if (xmlAttr != null)
            {
                WalkScore = System.Convert.ToBoolean(xmlAttr.Value);
            }

            // CrimeScore Attribute 
            xmlAttr = fetcherNode.Attributes[crimeScoreAttrString];
            if (xmlAttr != null)
            {
                CrimeScore = System.Convert.ToBoolean(xmlAttr.Value);
            }

            xmlAttr = fetcherNode.Attributes[zillowQueryAttrString];
            if (xmlAttr != null)
            {
                zillowRegionStats = System.Convert.ToBoolean(xmlAttr.Value);
            }

            _isActive = true;
            xmlAttr = fetcherNode.Attributes[activeAttrString];
            if (xmlAttr != null)
            {
                _isActive = System.Convert.ToBoolean(xmlAttr.Value);
            }


            // read fetcher Type  Attribute 
            xmlAttr = fetcherNode.Attributes[typeAttrStr];
            if (xmlAttr == null)
            {
                log.FatalFormat("AttributeMissing : " + typeAttrStr + " :  For fetcher Record ");
                return false;
            }

            _FetcherType = xmlAttr.Value;

            

            // Get The City Name , Btw it is optional 

            XmlNode cityNameNode = fetcherNode.SelectSingleNode(cityNameString);
            if (cityNameNode != null)
            {
                cityName = cityNameNode.InnerText;
                log.DebugFormat(" City Name Present :{0}", cityName);
            }

            // get PolyGon Co ordinates
            XmlNodeList geoPointNodes = fetcherNode.SelectNodes(GeoPointString);
            if( geoPointNodes == null )
            {
                log.DebugFormat("Geo Point List Missing ( Geo Polygon)  :  For fetcher Record ");
                return false;
            }

            log.DebugFormat("Reading GeoPoint/Polygon data ");
            foreach (XmlNode geoPoint in geoPointNodes)
            {
                GeoCoordinate gp = new GeoCoordinate();
                 xmlAttr = geoPoint.Attributes[latString];
                 if (xmlAttr == null)
                 {
                     return false;
                 }
                 gp.Lat = System.Convert.ToDouble(xmlAttr.Value);

                 xmlAttr = geoPoint.Attributes[longString];
                 if (xmlAttr == null)
                 {
                     return false;
                 }
                 gp.Long = System.Convert.ToDouble(xmlAttr.Value);

                // Add to geopoint lust 
                 _polygonGeopoints.Add(gp);
            }

            // Add Code to Read PolyGon and Radius 

            XmlNode geoRadiusNode = fetcherNode.SelectSingleNode(GeoRadiusString);
            if (geoRadiusNode != null)
            {
                var gp = new GeoCoordinate();
                xmlAttr = geoRadiusNode.Attributes[latString];
                if (xmlAttr == null)
                {
                    return false;
                }
                gp.Lat = System.Convert.ToDouble(xmlAttr.Value);

                xmlAttr = geoRadiusNode.Attributes[longString];
                if (xmlAttr == null)
                {
                    return false;
                }
                gp.Long = System.Convert.ToDouble(xmlAttr.Value);

                xmlAttr = geoRadiusNode.Attributes[radiusString];
                if (xmlAttr == null)
                {
                    return false;
                }

                var radius = System.Convert.ToDouble(xmlAttr.Value);

                // Reset PolyGon points and add rectangle.. 
                _polygonGeopoints.Clear();
                // Now we have to compute bounding rectangle 
                List < GeoCoordinate> geoList = GetCirclePoints(gp, radius);
                ProcessPointsAndGiveRectangle(geoList);
                log.DebugFormat(" GeoPoint Radius   Present ");
            }

            


            log.DebugFormat("Finsihed Reading Fetcher Record for " + FetcherName);
            return true;
        }


        private List<GeoCoordinate> GetCirclePoints(GeoCoordinate gp, double radius)
        {
            
            double lat1 = gp.Lat*Math.PI/180.0;
            double lon1 = gp.Long*Math.PI/180.0;
            double d = radius/3956;
            var listRect = new List<GeoCoordinate>();
            for (int x = 0; x <= 360; x += 90)
            {
                int tmpX = x;

                tmpX %= 360;
                double tc = (tmpX/90)*Math.PI/2;
                double lat = Math.Asin(Math.Sin(lat1)*Math.Cos(d) + Math.Cos(lat1)*Math.Sin(d)*Math.Cos(tc));
                lat = 180.0*lat/Math.PI;
                double lon;
                if (Math.Cos(lat1) == 0.0)
                {
                    lon = gp.Long; // endpoint a pole 
                }
                else
                {
                    lon = ((lon1 - Math.Asin(Math.Sin(tc)*Math.Sin(d)/Math.Cos(lat1)) + Math.PI)%(2*Math.PI)) - Math.PI;
                }
                lon = 180.0*lon/Math.PI;
                listRect.Add(new GeoCoordinate(lat, lon));
                //_polygonGeopoints.Add(new GeoCoordinate(lat, lon));
            } 

            /*
            int earthRadius = 6371;

            //latitude in radians
            double lat = (gp.Lat*Math.PI)/180;

            //longitude in radians
            double lon = (gp.Long*Math.PI)/180;

            var listRect = new List<GeoCoordinate>();


            //angular distance covered on earth's surface
            double d = radius/earthRadius;
            
            for (int i = 0; i <= 360; i += 90)
            {
                double bearing = i*Math.PI/180; //rad
                double tmpLat  = Math.Asin(Math.Sin(lat)*Math.Cos(d) +
                                           Math.Cos(lat)*Math.Sin(d)*Math.Cos(bearing));
                double tmpLong  = ((lon + Math.Atan2(Math.Sin(bearing)*Math.Sin(d)*Math.Cos(lat),
                                                     Math.Cos(d) - Math.Sin(lat) * Math.Sin(tmpLat))) * 180) / Math.PI;
                tmpLat = (tmpLat * 180) / Math.PI;
                listRect.Add(new GeoCoordinate(lat, lon));
            }*/

            return listRect;
        }


        public void ProcessPointsAndGiveRectangle(List<GeoCoordinate> myFavsList)
        {
            GeoRect locRect = null;
            List<GeoCoordinate> workingList = myFavsList;


            if (workingList.Count > 0)
            {
                var southEast = new GeoCoordinate(workingList[0]);
                var northWest = new GeoCoordinate(workingList[0]);
                var northEast = new GeoCoordinate();
                var southWest = new GeoCoordinate();

                foreach (GeoCoordinate loc in workingList)
                {
                    if (northWest.Long > loc.Long)
                    {
                        northWest.Long = loc.Long;
                    }

                    if (southEast.Long < loc.Long)
                    {
                        southEast.Long = loc.Long;
                    }

                    if (northWest.Lat < loc.Lat)
                    {
                        northWest.Lat = loc.Lat;
                    }

                    if (southEast.Lat > loc.Lat)
                    {
                        southEast.Lat = loc.Lat;
                    }
                }

                northEast.Long = southEast.Long;
                northEast.Lat = northWest.Lat;

                southWest.Long = northWest.Long;
                southWest.Lat = southEast.Lat;

                locRect = new GeoRect();
                locRect.northEast = northEast;
                locRect.southWest = southWest;
                locRect.northWest = northWest;
                locRect.southEast = southEast;
                _polygonGeopoints.Add(northWest);
                _polygonGeopoints.Add(northEast);
                _polygonGeopoints.Add(southEast);
                _polygonGeopoints.Add(southWest);
                _polygonGeopoints.Add(northWest);

                log.DebugFormat("***********  To Plot... ***********");
                log.DebugFormat("{0},{1}", northWest.Lat, northWest.Long);
                log.DebugFormat("{0},{1}", northEast.Lat, northEast.Long);
                log.DebugFormat("{0},{1}", southEast.Lat, southEast.Long);
                log.DebugFormat("{0},{1}", southWest.Lat, southWest.Long);

            }
        }

    }
}

